[iOS] 簡単にシステムのキーチェーンを操作できるライブラリ、SSKeychainについて
1 はじめに
(1) SSKeychainとは
iOSでは、パスワードを安全に保存できる仕組みとしてKeychain Serviceを提供しています。
https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html
しかし、これを利用するには、結構たくさんのコーディングが必要になります。
これに対して、SSKeychainは、このような処理をラッピングして簡単に記述できるフレームワークとなっています。
SSKeychainは、MITライセンスで公開されており、CocoaPodで簡単にインストールが可能です。
pod 'SSKeychain', '~> 1.3'
なお、2016年2月現在の最新バージョンは1.3.1です。
(2) システムデータ
キーチェーンは、アプリごとのデータではなく、端末単位で管理されるものです。 従って、NSUserDefaultsのように、アプリを削除しても消えることはありません。 削除が必要な場合は、明示的に作業する必要があります。
SSKeychainでは、NSUserDefaults並みに、簡単にデータ保存ができますので、永久的なデータ保存が必要な場合は応用できるかも知れません。
(3) 2種類の使用方法
SSKeychainは、以下の4つのファイルで構成されており、使い方としてSSKeychainとSSKeychainQueryの2種類があります。
- SSKeychain.h
- SSKeychain.m
- SSKeychainQuery.h
- SSKeychainQuery.m
2 SSKeychain
1つ目の使い方としてのSSKeychainでは、クラスメソッドを使用してキーチェンを操作します。
(1) クラスメソッド
主要なクラスメソッドは、次の通りです。
// 全てのキーチェンの列挙 + (NSArray *)allAccounts; // サービス名を指定しての列挙 + (NSArray *)accountsForService:(NSString *)serviceName; // サービス名とアカウント名を指定してパスワードを取得する + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account; // サービス名とアカウント名を指定してキーチェーン自体を削除する(全キーチェーン数自体が減る) + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account; // サービス名とアカウント名を指定してパスワードを変更する + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;
(2) 動作確認
簡単なサンプルを作成して、動作を確認してみました。
先ずは、端末に現在格納されているキーチェーンを列挙してUITextViewに表示するメソッドです。 全キーチェーンを列挙した後、それぞれのサービス名とアカウント名を取得して、パスワードを読みだしています。
// 列挙 - (void)refreshTextView { NSString *text = @""; int n=1; NSArray *data = [SSKeychain allAccounts]; for( NSDictionary *d in data){ NSString *service = d[kSSKeychainWhereKey]; NSString *account = d[kSSKeychainAccountKey]; NSString *password = [SSKeychain passwordForService:service account:account]; text = S=%@ A=%@ P=%@\n",n++,service,account,password]]; } self.textView.text = text; }
続いて、Saveボタンを押した時のコードです。 UITextFieldに入力された、サービス名、アカウント名、パスワードを>setPassword:で保存しています。
すでに、同じサービス名とアカウントのキーがある場合は、上書きになります。
// 保存 - (IBAction)tapSaveButton:(id)sender { NSString *service = self.serviceTextField.text; NSString *account = self.accountTextField.text; NSString *password = self.passwordTextField.text; if ( service.length != 0 && account.length != 0 && password.length != 0){ NSError *error; [SSKeychain setPassword:password forService:service account:account error:&error]; } [self refreshTextView]; // 再表示 }
最後にDeleteボタンを押した時のコードです。 設定された、サービス名とアカウント名を使用して、deletePasswordForService:を呼び出しています。 サービス名及び、アカウント名が一致したデータは削除されます。
パスワードは、分からなくても削除可能です。
// 削除 - (IBAction)tapDeleteButton:(id)sender { NSString *service = self.serviceTextField.text; NSString *account = self.accountTextField.text; if ( service.length != 0 && account.length != 0){ NSError *error; [SSKeychain deletePasswordForService:service account:account error:&error]; } [self refreshTextView]; // 再表示 }
動作確認中の画面です。
3 SSKeychainQuery
2つ目の使用方法であるSSKeychainQueryでは、インスタンスを生成し、プロパティ及び、インスタンスメソッドからキーチェーンを操作します。
(1) プロパティとインスタンスメソッド
主要なプロパティは、次の通りです。
// サービス名 @property (nonatomic, copy) NSString *service; // アカウント名 @property (nonatomic, copy) NSString *account; // ラベル @property (nonatomic, copy) NSString *label; // パスワード @property (nonatomic, copy) NSData *passwordData; @property (nonatomic, copy) id<NSCoding> passwordObject; @property (nonatomic, copy) NSString *password;
続いてインスタンスメソッドです。全体的に、DBへのアクセス風になってます。
// サービス名・アカウント名・パスワードを指定して保存する // サービス名・アカウント名の一致するキーチェンが存在する場合は、パスワードの変更になる - (BOOL)save:(NSError **)error; // サービス名とアカウント名を指定してキーチェーン自体を削除する(全キーチェン数が減る) - (BOOL)deleteItem:(NSError **)error; // サービス名とアカウント名を指定してパスワードを取得する - (NSArray *)fetchAll:(NSError **)error; // 全てのキーチェンの列挙 - (BOOL)fetch:(NSError **)error;
(2) 動作確認
先のSSKeychainの動作確認と同様の機能を実装してみます。
先ずは、全キーチェーンを列挙してUITextViewに表示するメソッドです。
fetchAll:で全てのキーチェーンを列挙した後、サービス名、アカウント名及び、パスワードの取得要領は、SSKeychainと同じです。
// 列挙 - (void)refreshTextView { NSString *text = @""; NSError *error = nil; SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; NSArray *data = [query fetchAll:&error]; int n=1; for( NSDictionary *d in data){ NSString *service = d[kSSKeychainWhereKey]; NSString *account = d[kSSKeychainAccountKey]; NSString *password = [SSKeychain passwordForService:service account:account]; text = S=%@ A=%@ P=%@\n",n++,service,account,password]]; } }
続いて、Saveボタンを押した時のコードです。 UITextFieldに入力された、サービス名、アカウント及び、パスワードをプロパティにセットしてsave:で保存しています。
既に、同じサービス名とアカウント名のキーチェーンがある場合は、上書きになります。
// 保存 - (IBAction)tapSaveButton:(id)sender { - NSError *error = nil; SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = self.serviceTextField.text; query.account = self.accountTextField.text; query.password = self.passwordTextField.text; [query save:&error]; [self refreshTextView]; // 再表示 }
最後にDeleteボタンを押した時のコードです。
サービス名とアカウント名をプロパティにセットして、deleteItem:を呼び出しています。 サービス名とアカウント名が一致したデータは削除されます。
パスワードは、分からなくても削除可能です。
// 削除 - (IBAction)tapDeleteButton:(id)sender { NSError *error = nil; SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = self.serviceTextField.text; query.account = self.accountTextField.text; [query deleteItem:&error]; [self refreshTextView]; // 再表示 }
実行画面については、先と同じなので、割愛します。
(3) NSString以外
プロパティを見てお気付きの方もおられると思いますが、SSKeychainQueryでは、パスワードのプロパティにNSString以外のものがあります。
passwordData及びpasswordObjectが、それです。
// パスワード @property (nonatomic, copy) NSData *passwordData; @property (nonatomic, copy) id<NSCoding> passwordObject; @property (nonatomic, copy) NSString *password;
こちらを利用すると、NSString以外のものが保存可能です。
下記のコードは、GitHubにあるテストコードの一部です。
NSDictionaryのデータを保存しているのが分かります。
https://github.com/soffes/sskeychain/blob/master/Tests/SSKeychainTests.m
- (void)testPasswordObject { SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = kSSKeychainServiceName; query.account = kSSKeychainAccountName; NSDictionary *dictionary = @{@"number": @42, @"string": @"Hello World"}; query.passwordObject = dictionary; NSError *error; XCTAssertTrue([query save:&error], @"Unable to save item: %@", error); query = [[SSKeychainQuery alloc] init]; query.service = kSSKeychainServiceName; query.account = kSSKeychainAccountName; query.passwordObject = nil; XCTAssertTrue([query fetch:&error], @"Unable to fetch keychain item: %@", error); XCTAssertEqualObjects(query.passwordObject, dictionary, @"Passwords were not equal"); }
4 最後に
SSKeychainQueryのおかげで、システムのキーチェーンが、非常に簡単に扱えます。 先人の功績に感謝です。
5 参考資料
GitHub sskeychain
http://cocoadocs.org/docsets/SSKeychain/1.3.1/